package com.sunteam.flutter_oss_plugin

import android.content.Context
import android.util.Log
import android.widget.Toast
import androidx.annotation.NonNull
import com.alibaba.sdk.android.oss.ClientException
import com.alibaba.sdk.android.oss.ServiceException
import com.alibaba.sdk.android.oss.callback.OSSCompletedCallback
import com.alibaba.sdk.android.oss.callback.OSSProgressCallback
import com.alibaba.sdk.android.oss.internal.OSSAsyncTask
import com.alibaba.sdk.android.oss.model.PutObjectRequest
import com.alibaba.sdk.android.oss.model.PutObjectResult
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch

/** FlutterOssPlugin */
class FlutterOssPlugin : FlutterPlugin, MethodCallHandler {
    /// The MethodChannel that will the communication between Flutter and native Android
    ///
    /// This local reference serves to register the plugin with the Flutter Engine and unregister it
    /// when the Flutter Engine is detached from the Activity
    private lateinit var channel: MethodChannel
    private var ossManager: OssManager? = null
    private var customManager: CustomOssManager? = null
    lateinit var ctx: Context
    private var ossAsyncTaskMap: HashMap<String, OSSAsyncTask<PutObjectResult>> = hashMapOf()

    override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
        channel = MethodChannel(flutterPluginBinding.binaryMessenger, "flutter_oss_plugin")
        ctx = flutterPluginBinding.applicationContext
        channel.setMethodCallHandler(this)

    }

    override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {

        var methodBeCatch = false
        methodBeCatch = customManager?.checkMethod(call, result, ctx) ?: false
        if (methodBeCatch) return
        when (call.method) {
            "initOss" -> {
                if (ossManager == null) ossManager = OssManager()
                ossManager?.initOss(
                    endPoint = Util.getCallArgument(call, "endPoint") ?: "",
                    stsServer = Util.getCallArgument(call, "stsServer") ?: "",
                    userAgent = Util.getCallArgument(call, "userAgent") ?: "",
                    needLog = Util.getCallArgument<Boolean>(call, "needLog") ?: false,
                    needDns = Util.getCallArgument<Boolean>(call, "needDns") ?: false,
                    maxConcurrentNumber = Util.getCallArgument<Int>(call, "maxConcurrencyNum") ?: 5,
                    maxRetryNumber = Util.getCallArgument<Int>(call, "retryNum") ?: 2,
                    connectTimeOut = Util.getCallArgument<Int>(call, "connectOutTime") ?: 15 * 1000,
                    socketTimeOut = Util.getCallArgument<Int>(call, "socketOutTime") ?: 15 * 1000,
                    ctx = ctx
                )
                result.success("");
            }
            "uploadFile" -> {
                Toast.makeText(
                    ctx,
                    "this is isSign ${Util.getCallArgument<Boolean>(call, "isSign") ?: false}",
                    Toast.LENGTH_LONG
                ).show()
                var backTag = Util.getCallArgument<String>(call, "backTag")!!
                if (ossManager == null) {
                    result.error(
                        "-1",
                        "manager unInit",
                        "please call initOss before call this method"
                    )
                    return
                }

                var bucketName = Util.getCallArgument<String>(call, "bucketName")

                if (bucketName == null || bucketName.isEmpty()) {
                    result.error("-1", "bucketName isEmpty", "please set parame bucketName")
                    return
                }
                var objectName = Util.getCallArgument<String>(call, "objectName")

                val ossAsyncTask = ossManager?.uploadFileByPath(
                    bucketName,
                    objectName = objectName
                        ?: "",
                    filePath = Util.getCallArgument<String>(call, "filePath")
                        ?: "",
                    progressCallback = OSSProgressCallback<PutObjectRequest> { request, currentSize, totalSize ->
                        GlobalScope.launch(Dispatchers.Main) {
                            var progress = (currentSize * 100.0 / totalSize)
                            channel.invokeMethod(
                                "progressCallBack", mapOf(
                                    "tag" to backTag,
                                    "progress" to progress
                                )
                            )
                        }

                    },
                    resultCallBack = object :
                        OSSCompletedCallback<PutObjectRequest, PutObjectResult> {
                        override fun onSuccess(
                            request: PutObjectRequest?,
                            ossResult: PutObjectResult?
                        ) {
                            if (!ossAsyncTaskMap.containsKey(backTag)) {
                                result.error("-1", "Task not found", "Task canceled")
                                return
                            }
                            var serverJson =
                                ossManager?.getFileOssPath(
                                    bucketName,
                                    objectName ?: "",
                                    Util.getCallArgument<Boolean>(call, "isSign") ?: false
                                )
                            GlobalScope.launch(Dispatchers.Main) {
                                channel.invokeMethod(
                                    "completeCallBack",
                                    mapOf("tag" to backTag, "requestId" to serverJson)
                                )
                                result.success(serverJson)
                            }

                        }

                        override fun onFailure(
                            request: PutObjectRequest?,
                            clientException: ClientException?,
                            serviceException: ServiceException?
                        ) {
                            if (clientException != null) {
                                GlobalScope.launch(Dispatchers.Main) {
                                    channel.invokeMethod(
                                        "clientError",
                                        mapOf(
                                            "tag" to backTag,
                                            "errorMessage" to clientException.message
                                        )
                                    )
                                }

                            } else if (serviceException != null) {
                                GlobalScope.launch(Dispatchers.Main) {
                                    channel.invokeMethod(
                                        "serverError", mapOf(
                                            "tag" to backTag,
                                            "errorMessage" to serviceException.rawMessage,
                                            "code" to serviceException.errorCode,
                                            "hostId" to serviceException.hostId
                                        )
                                    )
                                }

                            }
                        }
                    })

                ossAsyncTask?.let { ossAsyncTaskMap?.put(backTag, it) }
            }
            "getFilePath" -> {
                val bucketName = Util.getCallArgument<String>(call, "bucketName")

                if (bucketName == null || bucketName.isEmpty()) {
                    result.error("-1", "bucketName isEmpty", "please set parame bucketName")
                    return
                }
                val objectName = Util.getCallArgument<String>(call, "objectName")
                val resultString = GlobalScope.launch (Dispatchers.IO){
                    val path = async {
                        ossManager?.getFileOssPath(
                            bucketName,
                            objectName ?: "",
                            Util.getCallArgument<Boolean>(call, "isSign") ?: false
                        )
                    }
                    val resultString = path.await()
                    launch (Dispatchers.Main) {
                        result.success(resultString)
                    }
                }
               

            }
            "cancelTask" -> {

                var backTag = Util.getCallArgument<String>(call, "backTag")!!
                var ossAsyncTask: OSSAsyncTask<PutObjectResult>? = ossAsyncTaskMap?.get(backTag)

                var mapResult = excuteCancel(backTag, ossAsyncTask)
                ossAsyncTaskMap?.remove(backTag)
                Log.e("OssManager", "cancelAllTask $mapResult")
                GlobalScope.launch(Dispatchers.Main) {
                    channel.invokeMethod("cancelTask", mapOf("tasks" to arrayListOf(mapResult)))
                }
            }
            "cancelAllTask" -> {
                Log.e("OssManager", "cancelAllTask :$ossAsyncTaskMap")
                val tasks = arrayListOf<Map<String, Any>>()
                ossAsyncTaskMap?.forEach {
                    var mapResult = excuteCancel(it?.key, it?.value)
                    Log.e("OssManager", "cancelAllTask $mapResult")
                    tasks.add(mapResult)
                }
                ossAsyncTaskMap?.clear()
                GlobalScope.launch(Dispatchers.Main) {
                    channel.invokeMethod("cancelTask", mapOf("tasks" to tasks))
                }
            }
            "buildCustomManager" -> {
                customManager = CustomOssManager(channel)
            }
            "destroyManager"->{
                ossManager = null
                result.success(true)
            }
        }
    }

    private fun excuteCancel(
        tag: String,
        ossAsyncTask: OSSAsyncTask<PutObjectResult>?
    ): Map<String, Any> {
        var code = 0
        var msg = "结束任务失败"
        if (ossAsyncTask?.isCompleted!!) {
            code = 2
            msg = "任务已完成无法结束"
        } else if (!ossAsyncTask?.isCanceled!!) {
            code = 1
            msg = "任务已取消"
            ossAsyncTask?.cancel()
        }
        return mapOf("tag" to tag, "code" to code, "msg" to msg)
    }


    override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
        channel.setMethodCallHandler(null)
    }
}
